import math

from basis import *
from line import Line
from subpath import Subpath


def generate_parallel_fill(subpaths: list[Subpath], max_y: float, y_step: float, fillrule: str,
                           no_combine_infill: bool) -> list[Subpath]:
    num_fill_lines = int(max_y / y_step)
    fill = [list() for _ in range(num_fill_lines)]  # type: list[list[tuple[float, float]]]
    res = [list() for _ in range(num_fill_lines)]  # type: list[list[Line]]
    print(f'processing {len(subpaths)} subpaths with {sum(len(s.elements) for s in subpaths)} elements')
    for s in subpaths:
        for e in s.elements:
            e_fill_min_idx = math.ceil(e.min_y / y_step)
            e_fill_max_idx = math.floor(e.max_y / y_step)
            if e_fill_min_idx > e_fill_max_idx:
                continue  # falls between the cracks
            for i in range(e_fill_min_idx, e_fill_max_idx + 1):
                y = i * y_step
                line_spec = e.line_spec(y)
                x_min, x_max, direction = line_spec
                if direction == 0:
                    continue
                assert x_min == x_max
                line_spec = x_min, direction
                fill[i].append(line_spec)
    for fill_lines in fill:
        fill_lines.sort(key=lambda x: x[0])  # sort ascending
        i = 0
        while i < len(fill_lines) - 1:
            x, direction = fill_lines[i]
            if -1 < direction < 1:
                # direction == +/- 0.5
                if i < len(fill_lines) - 1:
                    x1, direction1 = fill_lines[i + 1]
                    if direction == direction1:
                        # coalesce
                        fill_lines[i] = x, direction + direction1
                        del fill_lines[i + 1]
                    elif direction == -direction1:
                        # ignore such line
                        del fill_lines[i:i+2]
                    else:
                        print(f'WARNING: inconsistent nearby line segments: {fill_lines[i]} {fill_lines[i+1]}')
                        fill_lines[i][1] = direction * 2  # don't leave non-integer direction values
            i += 1
    fill_rule_non_zero = fillrule == 'nonzero'
    for i, fill_lines in enumerate(fill):
        y = i * y_step
        num_intersections = 0
        start_x = None
        for x, direction in fill_lines:
            if num_intersections == 0 if fill_rule_non_zero else num_intersections & 1 == 0:
                start_x = x
            num_intersections += direction
            if num_intersections == 0 if fill_rule_non_zero else num_intersections & 1 == 0:
                if start_x is not None:
                    res[i].append(Line(False, start_x, y, x, y))
                else:
                    print(f'WARNING: start_x is None')  # TODO better error message
                start_x = None
        # break
    if no_combine_infill:
        return [Subpath.from_line(line) for lines in res for line in lines]
    regions = []
    num_lines = sum(len(lines) for lines in res)
    num_separate_infill_lines = num_lines
    start_y = 0
    while num_lines > 0:
        while len(res[start_y]) == 0:
            start_y += 1
        assert start_y < len(res)  # sanity
        line = res[start_y].pop(0)
        num_lines -= 1
        region = []
        direction = 0
        # find all lines below adjacent to the current line, merge them together into path
        y = start_y + 1
        while y < len(res):
            candidate_next = [(i, l) for i, l in enumerate(res[y]) if line.adjacent_x(l)]  # res[y] is sorted by x
            if len(candidate_next) == 0:
                if direction == 0:
                    region.append(line)
                break
            if direction == 0:
                direction = (1 if abs(line.max_x - candidate_next[-1][1].max_x) <
                                  abs(line.min_x - candidate_next[0][1].min_x) else -1)
                assert len(region) == 0
                if direction == -1:
                    line = line.flip()
                region.append(line)
            next_line_i, next_line = candidate_next[-1 if direction == 1 else 0]
            del res[y][next_line_i]
            if direction == 1:
                next_line = next_line.flip()
            num_lines -= 1
            # ensure that connecting line does not intersect non-filled region
            region.append(Line(False, line.x2, line.y2, next_line.x1, next_line.y1))
            region.append(next_line)
            direction = -direction
            line = next_line
            y += 1
        regions.append(region)
    assert num_lines == 0, num_lines
    print(f'found {len(regions)} fill regions from {num_separate_infill_lines} separate infill lines')
    return [Subpath.from_lines(region) for region in regions]


def generate_loop_fill(subpaths: list[Subpath], offset_amount: float, fillrule: str) -> list[Subpath]:
    res = []
    for s in subpaths:
        for i in range(4):
            res.append(s.offset((i + 1) * offset_amount))
    return res
